#include "FilterProcessor.h"
#include "FilterMaskFactory.h"
#include "MemoryHelper.h"
#include "PixelBuffer.h"
#include <cstring>
#include <cmath>
#include <limits>
#include <algorithm>
using namespace std;

FilterProcessor& FilterProcessor::Instance()
{
    static FilterProcessor instance;
    return instance;
}

void FilterProcessor::ApplyFilter(PixelBuffer* buffer, FilterType type, void* filterParameter)
{
    FilterMask* filterMask = FilterMaskFactory::GetFilterMask(type, filterParameter);
            
    Pixel** newBuffer = ApplyConvolution(buffer->Pixels(), buffer->Width(), buffer->Height(), filterMask->Mask, filterMask->Width, filterMask->Height, filterMask->PostProcessing);

    buffer->Pixels(newBuffer, buffer->Width(), buffer->Height());

    delete filterMask;
}

Pixel** FilterProcessor::ApplyConvolution(Pixel** buffer, int bufferWidth, int bufferHeight, float** mask, int maskWidth, int maskHeight, PostProcessingType postProcessing)
{
    int bufferCurrentX, bufferCurrentY;
    int maskCurrentX, maskCurrentY;
    int halfMaskWidth, halfMaskHeight;
    float newRVal;
    float newGVal;
    float newBVal;
    Pixel** newBuffer = MemoryHelper::Allocate2D<Pixel>(bufferWidth, bufferHeight);

    if(postProcessing == PostProcessing_Normalization)
    {
        ResetColorRanges();
    }

    for(int bufferY = 0; bufferY < bufferHeight; bufferY++)
    {
        for(int bufferX = 0; bufferX < bufferWidth; bufferX++)
        {
            newRVal = 0;
            newGVal = 0;
            newBVal = 0;
            maskCurrentY =  0;
            for(int offsetY = -maskHeight / 2; offsetY <= maskHeight / 2; offsetY++)
            {
                maskCurrentX = 0;
                for(int offsetX = -maskWidth / 2; offsetX <= maskWidth / 2; offsetX++)
                {
                    bufferCurrentX = bufferX + offsetX;
                    bufferCurrentY = bufferY + offsetY;

                    if (bufferCurrentX >= 0 && bufferCurrentX < bufferWidth &&
                        bufferCurrentY >= 0 && bufferCurrentY < bufferHeight)
                    {
                        newRVal += ((float)buffer[bufferCurrentY][bufferCurrentX].R * mask[maskCurrentY][maskCurrentX]);
                        newGVal += ((float)buffer[bufferCurrentY][bufferCurrentX].G * mask[maskCurrentY][maskCurrentX]);
                        newBVal += ((float)buffer[bufferCurrentY][bufferCurrentX].B * mask[maskCurrentY][maskCurrentX]);
                    }
                    maskCurrentX++;
                }
                maskCurrentY++;
            }
            
            SetNewPixel(&newBuffer[bufferY][bufferX], &buffer[bufferY][bufferX], newRVal, newGVal, newBVal, postProcessing);
        }  
    }

    if(postProcessing == PostProcessing_Normalization)
    {
        for(int bufferY = 0; bufferY < bufferHeight; bufferY++)
        {
            for(int bufferX = 0; bufferX < bufferWidth; bufferX++)
            {
                newBuffer[bufferY][bufferX].Normalize(oldMinColor, oldMaxColor, newMinColor, newMaxColor);
            }
        }
    }

    return newBuffer;
}


void FilterProcessor::SetNewPixel(Pixel* newPixel, Pixel* oldPixel, float newRVal, float newGVal, float newBVal, PostProcessingType postProcessing)
{
    if(postProcessing == PostProcessing_Normalization)
    {
        oldMinColor[0] = min(oldMinColor[0], newRVal);
        oldMinColor[1] = min(oldMinColor[1], newGVal);
        oldMinColor[2] = min(oldMinColor[2], newBVal);

        oldMaxColor[0] = max(oldMaxColor[0], newRVal);
        oldMaxColor[1] = max(oldMaxColor[1], newGVal);
        oldMaxColor[2] = max(oldMaxColor[2], newBVal);

        newMinColor[0] = min(newMinColor[0], (float)oldPixel->R);
        newMinColor[1] = min(newMinColor[1], (float)oldPixel->G);
        newMinColor[2] = min(newMinColor[2], (float)oldPixel->B);

        newMaxColor[0] = max(newMaxColor[0], (float)oldPixel->R);
        newMaxColor[1] = max(newMaxColor[1], (float)oldPixel->G);
        newMaxColor[2] = max(newMaxColor[2], (float)oldPixel->B);

        newPixel->R = newRVal;
        newPixel->G = newGVal;
        newPixel->B = newBVal;
    }
    else if(postProcessing == PostProcessing_CutOff)
    {
        newPixel->R = newRVal;
        newPixel->G = newGVal;
        newPixel->B = newBVal;

        newPixel->CutOff();
    }
    else
    {
        newPixel->R = newRVal;
        newPixel->G = newGVal;
        newPixel->B = newBVal;
    }
}

void FilterProcessor::ResetColorRanges()
{
    fill(oldMinColor, oldMinColor + 3, INT_MAX);
    fill(oldMaxColor, oldMaxColor + 3, INT_MIN);
    fill(newMinColor, newMinColor + 3, INT_MAX);
    fill(newMaxColor, newMaxColor + 3, INT_MIN);
}